//
// 编辑状态中 model
// 1. 管理编辑中的临时数据
// 2. 管理 onchange 队列,  对 onchange 依次串行操作
// 3. 最后一步的 write or create 也进入  onchange 队列

const cp = item => JSON.parse(JSON.stringify(item))

function is_virtual(rid) {
  return typeof rid === 'string'
}

class ChangeQueue {
  constructor() {
    this.queue = []
    this.call_id = 0
  }

  async wait_call() {
    const queue_in = [...this.queue]
    const queue = [...queue_in]
    let one = queue.shift()
    while (one) {
      await one.result
      one = queue.shift()
    }

    queue_in.forEach(item => {
      const index_to_del = this.queue.findIndex(
        item2 => item2.call_id === item.call_id
      )
      if (index_to_del >= 0) {
        this.queue.splice(index_to_del, 1)
      }
    })
  }
  append(result) {
    const call_id = this.call_id + 1
    this.call_id = call_id
    this.queue.push({ call_id, result })
  }
}

class EditBaseRoot {
  constructor(model_name, payload = {}) {
    // editmodel 管理 编辑中的数据
    // 从只读转换到编辑状态, metadata 已经提前已知
    // datastore 管理编辑中的数据
    // datastore.record 是原始只读数据
    // datastore.values 是编辑后的数据
    //
    // changeQueue 用于管理顺序队列. 以保证 onchange 函数顺序执行

    const { env, metadata, context = {} } = payload
    this._model = model_name
    this._env = env
    this._metadata = metadata
    this._context = context

    this.datastore = {
      record: {},
      values: {}
    }

    this.changeQueue = new ChangeQueue()
  }

  //
  // 基本属性
  //
  get model() {
    return this._model
  }

  get Model() {
    return this._env.model(this._model, { metadata: this.metadata })
  }

  get env() {
    return this._env
  }

  get context() {
    return this._context
  }

  get metadata() {
    return this._metadata
  }

  get record_display() {
    return this._merge_to_display(
      { ...this.datastore.record },
      { ...this.datastore.values }
    )
  }

  set_edit({ record = {}, values = {} }) {
    // 初始化 编辑状态
    this.datastore = {
      record: { ...record },
      values: { ...values }
    }

    return this.record_display
  }

  async _onchange(field_name, value) {
    // to override
  }

  async _wait() {
    //  等待其他 任务完成
    await this.changeQueue.wait_call()
  }

  async onchange_by_wait(field_name, value) {
    await this._wait()
    return await this._onchange(field_name, value)
  }

  async onchange(field_name, value) {
    const result = this.onchange_by_wait(field_name, value)
    this.changeQueue.append(result)
    return result
  }

  async _commit() {
    // to override
  }

  async commit_by_wait(validate) {
    //  等待其他 任务完成
    await this._wait()

    const call_validate = validate2 => {
      if (!validate2) {
        return true
      }

      return new Promise(resolve => {
        // 如果有校验函数, 则回调 校验函数
        validate2(validate_result => {
          resolve(validate_result)
        })
      })
    }

    const validate_result = await call_validate(validate)

    if (validate_result) {
      // 若校验 返回 true, 则 commit
      // console.log('validate ok', validate_result)
      return await this._commit()
    } else {
      console.log('validate err', validate_result)
      return
    }
  }

  async commit(validate) {
    const result = this.commit_by_wait(validate)
    return result
  }
}

class EditBaseMerge extends EditBaseRoot {
  constructor(model_name, payload = {}) {
    super(model_name, { ...payload })
  }

  _merge_data(record = {}, values = {}) {
    // call by this._merge_to_display
    // call by this._merge_to_onchange
    //
    // 将只读数据和编辑后的数据 merge. 格式化为标准编辑格式
    // 后续供其他地方使用
    // 1. 格式转换为 display. 供前端使用
    // 2. 格式转换为 onchange 参数用
    // 3. 格式转换为 write, create 参数用
    //
    const all_keys = Object.keys({ ...record, ...values })
    return all_keys.reduce((acc, fld) => {
      const meta = this.metadata[fld] || {}
      if (meta.type === 'many2many') {
        if (fld in values) {
          acc[fld] = values[fld]
        } else if (fld in record) {
          acc[fld] = record[fld]
        } else {
          acc[fld] = []
        }
      } else if (meta.type === 'one2many') {
        const Rel = this.env.one2many(meta, { context: this.context })
        const val = Rel._merge_data(record[fld], values[fld])
        acc[fld] = val
      } else {
        const val = fld in values ? values[fld] : record[fld]
        acc[fld] = val
      }

      return acc
    }, {})
  }

  _merge_to_display(record_in = {}, values = {}) {
    // call by this.record_display
    // 将标准格式转换为 显示格式供 前端页面直接显示用

    const record = this._merge_data(record_in, values)

    let record2 = Object.keys(record).reduce((acc, fld) => {
      const meta = this.metadata[fld] || {}
      if (meta.type === 'one2many') {
        const Rel = this.env.one2many(meta, { context: this.context })
        // console.log(record, meta)
        const vals = Rel._merge_to_display(record[fld])
        acc[fld] = vals
      } else {
        acc[fld] = record[fld]
      }

      return acc
    }, {})

    const compute_fns = {}

    Object.keys(this.metadata).forEach(fld => {
      const meta = this.metadata[fld]
      if (meta.odoojs_extend) {
        if (meta.compute && this.Model[meta.compute]) {
          compute_fns[meta.compute] = 1
        }
      }
    })

    Object.keys(compute_fns).forEach(fn => {
      const context = this.context
      // console.log('editmodle:', this, context)
      const res = this.Model[fn]({ record: record2, context })
      record2 = { ...record2, ...res }
    })

    return record2
  }

  _merge_to_modifiers(record_in = {}, values = {}) {
    // call by formview.get_arch_sheet
    const record = this._merge_data(record_in, values)

    const record2 = Object.keys(record).reduce((acc, fld) => {
      const meta = this.metadata[fld] || {}
      if (['many2many'].includes(meta.type)) {
        const vals = record[fld].map(item => item.id)
        acc[fld] = vals
      } else if (['one2many'].includes(meta.type)) {
        const Rel = this.env.one2many(meta, { context: this.context })
        const vals = Rel._merge_to_modifiers(record[fld])
        acc[fld] = vals
      } else if (['many2one'].includes(meta.type)) {
        acc[fld] = record[fld].id
      } else {
        acc[fld] = record[fld]
      }

      return acc
    }, {})

    return record2
  }

  _merge_to_onchange(record_in = {}, values = {}) {
    // call by this._onchange
    // 将标准格式转换为 onchange 参数格式
    //
    // console.log('_merge_to_onchange1', record_in, values)
    const record = this._merge_data(record_in, values)

    const record2 = Object.keys(record).reduce((acc, fld) => {
      const meta = this.metadata[fld] || {}
      if (fld === 'id') {
        const val = record[fld]
        if (!is_virtual(val)) {
          acc[fld] = val
        }
      } else if (meta.odoojs_extend) {
        //
      } else if (meta.type === 'many2many') {
        acc[fld] = [[6, false, record[fld].map(item => item.id)]]
      } else if (meta.type === 'one2many') {
        const Rel = this.env.one2many(meta, { context: this.context })
        const vals = Rel._merge_to_onchange(record[fld])
        acc[fld] = vals
      } else if (meta.type === 'many2one') {
        acc[fld] = record[fld].id
      } else {
        acc[fld] = record[fld]
      }

      return acc
    }, {})

    return record2
  }

  _merge_values(values1 = {}, values2 = {}) {
    // call by relation.js One2Many._merge_values
    // 明细行编辑后的合并
    return this._merge_after_onchange(values1, values2)
  }

  _merge_after_onchange(values1 = {}, values2 = {}) {
    // call by this._onchange
    // onchange 返回结果 在 model.js 中 已经标准化
    // 这里将结果 与 已编辑部分的内容 merge

    // 如果出现 嵌套 o2m 字段. 如何处理 todo. 这种情况, 需要找到例子
    //

    const all_keys = Object.keys({ ...values1, ...values2 })
    return all_keys.reduce((acc, fld) => {
      const meta = this.metadata[fld] || {}

      if (meta.type === 'one2many') {
        const Rel = this.env.one2many(meta, { context: this.context })
        const vals = [...(values1[fld] || []), ...(values2[fld] || [])]
        const vals2 = Rel._merge_values(vals)
        acc[fld] = vals2
      } else {
        const val = fld in values2 ? values2[fld] : values1[fld]
        acc[fld] = val
      }

      return acc
    }, {})
  }

  _merge_to_write(record = {}, values = {}) {
    // call by this._commit
    // call by relatin.One2many._merge_to_write
    const all_keys = Object.keys(values)

    const record2 = this._merge_to_modifiers(record, values)

    return all_keys.reduce((acc, fld) => {
      const meta = this.metadata[fld] || {}
      const isreadonly = merge_to_write_get_readonly(meta, record2)
      // console.log(fld, isreadonly, meta.type, meta)

      if (isreadonly) {
        // do nothing
      } else if (meta.type === 'one2many') {
        const Rel = this.env.one2many(meta, { context: this.context })
        const vals = Rel._merge_to_write(values[fld])
        acc[fld] = vals
      } else if (meta.type === 'many2many') {
        const val = values[fld]
        acc[fld] = [[6, false, val.map(item => item.id)]]
      } else if (meta.type === 'many2one') {
        const val = values[fld]
        acc[fld] = val.id
      } else {
        const val = values[fld]
        acc[fld] = val
      }

      return acc
    }, {})
  }
}

class EditBase extends EditBaseMerge {
  constructor(model_name, payload = {}) {
    super(model_name, { ...payload })
  }

  async _onchange(field_name, value) {
    // console.log('_onchange', field_name, value)
    if (!field_name) {
      return this._onchange_new()
    }
    const meta = this.metadata[field_name]
    if (meta.odoojs_extend) {
      return this._onchange_odoojs(field_name, value)
    } else {
      const value_get = () => {
        const meta = this.metadata[field_name] || {}
        if (meta.type === 'one2many') {
          const Rel = this.env.one2many(meta, { context: this.context })
          const value2 = Rel._split_for_write(value)
          return value2
        } else {
          return value
        }
      }

      const new_values = { [field_name]: value_get() }
      this._onchange_merge_me(new_values)
      return this._onchange_old([field_name])
    }
  }

  async _onchange_merge_me(update_values) {
    const datastore = this.datastore

    const old_values = datastore.values
    const new_values = this._merge_after_onchange(old_values, update_values)

    datastore.values = {
      ...datastore.values,
      ...new_values
    }
  }

  async _onchange_odoojs(field_name, value) {
    const meta = this.metadata[field_name]
    const res = await this.Model[meta.inverse]({
      field_name,
      value,
      ...this.datastore,
      record_display: this.record_display
    })

    const { value: values2 = {} } = res
    this._onchange_merge_me(values2)

    const field_names = Object.keys(values2)
    return this._onchange_old(field_names)
  }

  _onchange_values_for_parent() {
    // 如果是子表, 需要
    return {}
  }

  async call_onchange(ids, values, field_name) {
    const context = this.context
    const field_names = Array.isArray(field_name) ? field_name : [field_name]
    return await this.Model.call_onchange(ids, values, field_names, {
      context
    })
  }

  async _onchange_new() {
    const datastore = this.datastore
    const record = datastore.record
    const values = datastore.values

    // new 的时候. 不能给默认值. 否则返回的结果中, onchange 的默认值就不对了
    // todo 2023-12-25 为什么要这样做? 先给个默认值?
    // ok: 后续 view arch 又需要有值. 二者矛盾, 只好在 onchange 之后 补充上默认值了

    const res_ids = []
    const vals_onchg2 = this._merge_to_onchange(record, values)
    const with_parent = this._onchange_values_for_parent()
    const vals_onchg = { ...vals_onchg2, ...with_parent }

    const field_names = []
    const res = await this.call_onchange(res_ids, vals_onchg, field_names)

    const { value: values2, domain: domain2 = {} } = res
    // console.log('onchange, res', res, values2)

    const values3 = { ...values2 }

    for (const fld of Object.keys(this.metadata)) {
      const meta = this.metadata[fld]
      if (!(fld in values3)) {
        if (meta.type === 'many2one') {
          values3[fld] = { id: null }
        } else if (['many2many', 'one2many'].includes(meta.type)) {
          values3[fld] = []
        } else {
          values3[fld] = null
        }
      }
    }

    datastore.values = {
      ...datastore.values,
      ...values3
    }
    return {
      values: datastore.values,
      domain: domain2,
      record_display: this.record_display
    }
  }

  async _onchange_old(field_names) {
    if (!field_names.length) {
      return {
        values: this.datastore.values,
        record_display: this.record_display
      }
    }

    const datastore = this.datastore
    const record = datastore.record
    const values = datastore.values

    // const res_ids = record.id ? [record.id] : []
    // 如果是  o2m 子表, id 可能是 virtual
    const res_ids = record.id && !is_virtual(record.id) ? [record.id] : []

    // // // values 中 可能有 id,  需要删除

    const vals_onchg2 = this._merge_to_onchange(record, values)
    // console.log('vals_onchg2', vals_onchg2)
    const with_parent = this._onchange_values_for_parent()
    const vals_onchg = { ...vals_onchg2, ...with_parent }

    const res = await this.call_onchange(res_ids, vals_onchg, field_names)

    const { value: values2, domain: domain2 = {} } = res

    datastore.values = this._merge_after_onchange(datastore.values, values2)

    return {
      values: datastore.values,
      domain: domain2,
      record_display: this.record_display
    }
  }
}

export class Editmodel extends EditBase {
  constructor(...args) {
    super(...args)
  }

  async _commit() {
    // call create or write
    const datastore = this.datastore
    const record = datastore.record
    const values = datastore.values

    console.log('commit', datastore)

    const res_id = record.id
    const vals = this._merge_to_write(record, values)
    // todo:  if vals 为空 且不是  wizard commit, 直接返回
    //

    console.log('commit', record, values, vals)
    const context = this.context
    const one = await this.Model.call_commit(res_id, vals, { context })

    // todo.
    // 现在页面 要求返回结果是 id. 是否改为 dict?
    //
    return one.id
  }

  get_fields_all(nodes) {
    // call by edit form view,
    //  to get all fields with meta and domain
    const nodes2 = this.Model.get_fields_all(nodes)
    const self = this
    function get_domain(nodeinfo) {
      const { meta } = nodeinfo
      const modifiers = 'domain'
      if (!(modifiers in meta)) {
        return
      }
      const domain_raw =
        modifiers in nodeinfo ? nodeinfo[modifiers] : meta[modifiers]

      if (typeof domain_raw === 'function') {
        const { record: record0, values } = self.datastore
        const record = self._merge_to_modifiers(record0, values)
        const context = self.context
        return domain_raw({ record, context })
      }
      return domain_raw
    }

    return Object.keys(nodes2).reduce((acc, fld) => {
      acc[fld] = { ...nodes2[fld] }
      const domain = get_domain(nodes2[fld])
      if (domain) {
        acc[fld].domain = domain
      }
      return acc
    }, {})
  }
}

export class EditX2m extends EditBase {
  constructor(model_name, payload = {}) {
    const { field_info, ...payload2 } = payload
    const metadata = field_info.metadata
    super(model_name, { ...payload2, metadata })

    this.field_info = field_info
    this.parent_info = {}
  }

  set_edit(payload) {
    const { parent_info } = payload
    const res = super.set_edit(payload)

    this.parent_info = { ...parent_info }

    return res
  }

  _onchange_values_for_parent() {
    const parent_info = this.parent_info

    const { metadata, model, record, values } = parent_info

    // todo? 嵌套 获取 parent editmodel
    const EM = this.env.editmodel(model, { metadata })
    const vals = EM._merge_to_onchange(record, values)
    // console.log('parent2', vals)

    const { relation_field } = this.field_info
    const values2 = { [relation_field]: vals }
    // values2 补上 子表的数据
    return values2
  }

  async _commit() {
    //  这里的作用是 排队, 等待 其他 onchange 完成
    //  之后, 返回 one form 的 values . 供 o2m tree 更新用

    const datastore = this.datastore

    return {
      values: datastore.values,
      record_display: this.record_display
    }
  }

  get_fields_all(nodes) {
    // call by edit form view,
    //  to get all fields with meta and domain

    const nodes2 = this.Model.get_fields_all(nodes)
    const self = this

    function get_parent_data() {
      const parent_info = self.parent_info
      const { metadata, model, record, values } = parent_info
      const EM = self.env.editmodel(model, { metadata })
      return EM._merge_to_modifiers(record, values)
    }

    function get_domain(nodeinfo) {
      const { meta } = nodeinfo
      const modifiers = 'domain'
      if (!(modifiers in meta)) {
        return
      }
      const domain_raw =
        modifiers in nodeinfo ? nodeinfo[modifiers] : meta[modifiers]
      if (typeof domain_raw === 'function') {
        const { record: record0, values } = self.datastore
        const record_without_prt = self._merge_to_modifiers(record0, values)
        const parent_data = get_parent_data()
        const record = { ...record_without_prt, parent: parent_data }

        const context = self.context
        return domain_raw({ record, context })
      }
      return domain_raw
    }

    return Object.keys(nodes2).reduce((acc, fld) => {
      acc[fld] = { ...nodes2[fld] }
      const domain = get_domain(nodes2[fld])
      if (domain) {
        acc[fld].domain = domain
      }
      return acc
    }, {})
  }
}

function merge_to_write_get_readonly(meta, record) {
  // todo . fields_get .readonly and viewxml readonly
  //

  // if (meta.force_save) {
  //   return false
  // }

  const meta_readonly_get = record2 => {
    if (typeof meta.readonly === 'function') {
      return meta.readonly({ record: record2 })
    } else {
      return meta.readonly
    }
  }

  const state = record.state

  if (meta.states === undefined) {
    return meta_readonly_get(record)
  }

  if (state && meta.states && meta.states[state]) {
    const readonly3 = meta.states[state].reduce((acc, cur) => {
      acc[cur[0]] = cur[1]
      return acc
    }, {})

    if (readonly3.readonly !== undefined) {
      return readonly3.readonly
    }
  }

  return meta_readonly_get(record)
}
